
package com.meixiaoliang.portal.core.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.meixiaoliang.portal.core.annotation.ExcelTitle;
import com.meixiaoliang.portal.modular.system.model.enums.ExcelTypeEnum;

/**
 * excel 工具类
 *
 * @author mei.xiaoliang@qq.com
 */
public class ExcelUtil {

    protected static Logger logger = LoggerFactory.getLogger(ExcelUtil.class);

    /**
     * 列表数据导出到 excel
     *
     * @param excelName excel 标题
     * @param sheetName sheet 标题, 传空为 Sheet1
     * @param type      excel 打印类型, 为了区分单实体类被多次打印
     * @param dataList  需要打印的列表数据
     */
    public static <T> void exportList(String excelName, String sheetName, ExcelTypeEnum type, List<T> dataList) {

        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setContentType("application/vnd.ms-excel;charset=utf-8");

        try {

            response.setHeader("Content-Disposition", "attachment; filename=" + new String(excelName.getBytes("gbk"), "iso8859-1") + ".xls");

            HSSFWorkbook workbook = new HSSFWorkbook();
            sheetName = StringUtils.isBlank(sheetName) ? "Sheet1" : sheetName;
            HSSFSheet sheet = workbook.createSheet(sheetName);

            if (dataList.size() >= 1) {

                // 所有属性
                Field[] fields = dataList.get(0).getClass().getDeclaredFields();
                List<Field> fieldList = new ArrayList<>();
                TreeMap<Integer, ExcelDto> treeMap = new TreeMap<>();
                // 获得所有需要导出的属性
                for (Field field : fields) {
                    ExcelTitle[] excelTitles = field.getDeclaredAnnotationsByType(ExcelTitle.class);
                    if (ArrayUtils.isNotEmpty(excelTitles)) {
                        for (ExcelTitle item : excelTitles) {
                            // 所有本次需要的属性
                            if (type == item.type()) {
                                System.out.println("属性名: " + field.getName() + " --- order: " + item.order() + ", value: " + item.value());
                                treeMap.put(item.order(), new ExcelDto(field.getName(), item.value()));
                            }
                        }
                    }
                }
                logger.info("需要打印 field: " + treeMap);

                // 设置表头
                int index = 0;
                HSSFRow row = sheet.createRow(index);
                for (Integer id : treeMap.keySet()) {
                    HSSFCell cell = row.createCell(id);
                    HSSFRichTextString textString = new HSSFRichTextString(treeMap.get(id).getValue());
                    logger.info("fieldValue: " + treeMap.get(id).getValue() + ", order: " + id);
                    cell.setCellValue(textString);
                }

                // 表格中填充数据
                for (Object obj : dataList) {
                    index++;
                    row = sheet.createRow(index);
                    for (Integer id : treeMap.keySet()) {
                        HSSFCell cellData = row.createCell(id);
                        HSSFRichTextString textData;
                        String fileldValue = getFieldValue(obj, treeMap.get(id).getFieldName());
                        if (StringUtils.isNotBlank(fileldValue)) {
                            textData = new HSSFRichTextString(fileldValue);
                        } else {
                            textData = new HSSFRichTextString("");
                        }
                        cellData.setCellValue(textData);
                    }
                }
            }

            workbook.write(response.getOutputStream());

        } catch (IOException | IllegalArgumentException e) {
            e.printStackTrace();
        } finally {
            try {
                response.getOutputStream().close();
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
    }

    /**
     * 对象根据属性名获得属性值
     *
     * @param owner     对象
     * @param fieldName 属性名
     * @return 实体对象反射获取值
     */
    private static String getFieldValue(Object owner, String fieldName) {
        Class<?> ownerClass = owner.getClass();
        String methodName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

        try {
            Type type = owner.getClass().getDeclaredField(fieldName).getGenericType();
            Method method = ownerClass.getMethod("get" + methodName);
            // LocalDateTime toString 需要去除 T
            if (type.equals(LocalDateTime.class)) {
                return method.invoke(owner).toString().replace("T", " ");
            }
            return method.invoke(owner).toString();
        } catch (Exception e) {
            logger.error(e.getMessage());
            return "";
        }
    }

    static class ExcelDto {

        private String fieldName;

        private String value;

        ExcelDto(String fieldName, String value) {
            this.fieldName = fieldName;
            this.value = value;
        }

        void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        String getFieldName() {
            return this.fieldName;
        }

        void setValue(String value) {
            this.value = value;
        }

        String getValue() {
            return this.value;
        }

    }

}
